(function() {
  init()

  function init() {
    window.$ = (s) => document.querySelector(s)
    window.$all = (s) => document.querySelectorAll(s)
    window.animate = animate
    HTMLElement.prototype.fadeOut = fadeOut
    HTMLElement.prototype.fadeIn = fadeIn
    HTMLElement.prototype.hide = hide
    HTMLElement.prototype.show = show
  }

  /**
   * @param {number} duration
   * @param {number} from
   * @param {number} to
   * @param {Function} onProgress
   * @param {Function} onFinish
   */
  function animate(duration, from, to, onProgress, onFinish) {
    return new Promise(resolve => {
      const s = to - from
      const v = s / duration
      const startTime = Date.now()
      let value = from
      onProgress(value)

      function _run() {
        const now = Date.now()
        const time = now - startTime
        if (time >= duration) {
          value = to
          onProgress(value)
          onFinish && onFinish()
          resolve()
          return
        }

        const d = time * v
        value = d + from
        onProgress(value)
        requestAnimationFrame(_run)
      }

      requestAnimationFrame(_run)
    })
  }

  function fadeIn(duration = 500, onProgress, onFinish) {
    return new Promise(resolve => {
      this.show()
      animate(duration, 0, 1, (val) => {
        this.style.opacity = val
      }, () => {
        this.style.removeProperty('opacity')
        onFinish && onFinish()
        resolve()
      })
    })
  }

  function fadeOut(duration = 500, onProgress, onFinish) {
    return new Promise(resolve => {
      animate(duration, 1, 0, (val) => {
        this.style.opacity = val
      }, () => {
        this.hide()
        this.style.removeProperty('opacity')
        onFinish && onFinish()
        resolve()
      })
    })
  }

  function hide() {
    this.style.display = 'none'
  }

  function show() {
    this.style.display = 'block'
  }
})()